// Magic Software, Inc.
// http://www.magic-software.com
// Copyright (c) 2000, All Rights Reserved
//
// Source code from Magic Software is supplied under the terms of a license
// agreement and may not be copied or disclosed except in accordance with the
// terms of that agreement.  The various license agreements may be found at
// the Magic Software web site.  This file is subject to the license
//
// RESTRICTED USE SOURCE CODE
// http://www.magic-software.com/License/restricted.pdf

#include "MgcObject.h"
#include "MgcRenderer.h"
#include "MgcStream.h"

const char MgcStream::ms_acVersion[MgcStream::VERSION_STRING_LENGTH] =
    "Magic3D Version 1.00";

const unsigned int MgcStream::ms_uiTopLevelGrowBy = 8;

//----------------------------------------------------------------------------
MgcStream::MgcStream ()
    :
    m_aspkTopLevel(ms_uiTopLevelGrowBy),
    m_kMap(32768)
{
    m_pkFile = 0;
}
//----------------------------------------------------------------------------
MgcStream::~MgcStream ()
{
    RemoveAll();
}
//----------------------------------------------------------------------------
bool MgcStream::Insert (MgcObject* pkObject)
{
    if ( pkObject  )
    {
        if ( MgcIsDerivedFromClass(MgcRenderer,pkObject) )
        {
            // renderers cannot be streamed
            return false;
        }

        unsigned int uiQuantity = m_aspkTopLevel.GetQuantity();
        unsigned int uiEmpty = uiQuantity;
        for (unsigned int uiI = 0; uiI < uiQuantity; uiI++)
        {
            // find first available slot
            if ( uiEmpty == uiQuantity && !m_aspkTopLevel[uiI] )
                uiEmpty = uiI;

            // check if object is already inserted
            if ( pkObject == m_aspkTopLevel[uiI] )
                return false;
        }

        if ( uiEmpty == uiQuantity )
        {
            // all slots filled, need to resize the array
            uiQuantity += ms_uiTopLevelGrowBy;
            m_aspkTopLevel.SetQuantity(uiQuantity,true);
        }

        m_aspkTopLevel[uiEmpty] = pkObject;
        return true;
    }

    return false;
}
//----------------------------------------------------------------------------
void MgcStream::RemoveAll ()
{
    unsigned int uiQuantity = m_aspkTopLevel.GetQuantity();
    for (unsigned int uiI = 0; uiI < uiQuantity; uiI++)
        m_aspkTopLevel[uiI] = 0;
}
//----------------------------------------------------------------------------
unsigned int MgcStream::GetObjectCount ()
{
    unsigned int uiQuantity = m_aspkTopLevel.GetQuantity();
    for (unsigned int uiI = 0; uiI < uiQuantity; uiI++)
    {
        if ( m_aspkTopLevel[uiI] == 0 )
            return uiI;
    }
    return uiQuantity;
}
//----------------------------------------------------------------------------
MgcObject* MgcStream::GetObjectAt (unsigned int uiIndex) const
{
    if ( uiIndex < m_aspkTopLevel.GetQuantity() )
        return m_aspkTopLevel[uiIndex];
    else
        return 0;
}
//----------------------------------------------------------------------------
bool MgcStream::IsTopLevel (MgcObject* pkObject)
{
    unsigned int uiQuantity = m_aspkTopLevel.GetQuantity();
    for (unsigned int uiI = 0; uiI < uiQuantity; uiI++)
    {
        if ( pkObject == m_aspkTopLevel[uiI] )
            return true;
    }
    return false;
}
//----------------------------------------------------------------------------
bool MgcStream::Load (const char* acFilename)
{
    FILE* pkFile;
    if ( fopen_s(&pkFile, acFilename,"rb") )
        return false;

    // get the file version
    char* acString = new char[VERSION_STRING_LENGTH];
    fread(acString,sizeof(char),VERSION_STRING_LENGTH,m_pkFile);
    if ( strcmp(acString,ms_acVersion) != 0 )
    {
        delete[] acString;
        fclose(m_pkFile);
        return false;
    }
    delete[] acString;

    // clear out all previous top level objects
    RemoveAll();

    // load list of unique objects
    MgcObject* pkObject;
    while ( true )
    {
        // read "Top Level" or RTTI name
        ReadString(acString);
        if ( feof(m_pkFile) )
            break;

        bool bTopLevel = ( strcmp(acString,"Top Level") == 0 );
        if ( bTopLevel )
        {
            // read RTTI name
            delete[] acString;
            ReadString(acString);
        }

        // get the factory function for the type of object about to be read
        MgcString kString(acString);
        MgcFactoryFunction oFactory;
        bool bFound = MgcObject::ms_pkFactory->GetAt(kString,oFactory);
        assert( bFound );
        delete[] acString;
        if ( !bFound )
        {
            // cannot find factory function
            fclose(m_pkFile);
            return false;
        }

        // load the object
        pkObject = oFactory(*this);

        // keep track of all top level objects for application use
        if ( bTopLevel )
            Insert(pkObject);
    }

    // link the objects
    MgcObject* pkLinkID;
    MgcStream::Link* pkLink;
    bool bFound = m_kMap.GetFirst(pkLinkID,(void*&)pkLink);
    while ( bFound )
    {
        pkObject = pkLink->GetObject();
        pkObject->Link(*this,pkLink);
        bFound = m_kMap.GetNext(pkLinkID,(void*&)pkLink);
    }

    // delete the stream link records
    bFound = m_kMap.GetFirst(pkLinkID,(void*&)pkLink);
    while ( bFound )
    {
        delete pkLink;
        bFound = m_kMap.GetNext(pkLinkID,(void*&)pkLink);
    }

    m_kMap.RemoveAll();

    return fclose(m_pkFile) == 0;
}
//----------------------------------------------------------------------------
bool MgcStream::Save (const char* acFilename)
{
    FILE* pkFile;
    if ( fopen_s(&pkFile, acFilename,"wb") )
        return false;

    // file version
    fwrite(ms_acVersion,sizeof(char),VERSION_STRING_LENGTH,m_pkFile);

    // build list of unique objects
    for (unsigned int uiI = 0; uiI < m_aspkTopLevel.GetQuantity(); uiI++)
    {
        if ( m_aspkTopLevel[uiI] )
            m_aspkTopLevel[uiI]->Register(*this);
    }

    // save list of unique objects
    MgcObject* pkObject;
    MgcObject* pkDummy;
    bool bFound = m_kMap.GetFirst(pkObject,(void*&)pkDummy);
    while ( bFound )
    {
        if ( IsTopLevel(pkObject) )
            WriteString("Top Level");
        pkObject->Save(*this);
        bFound = m_kMap.GetNext(pkObject,(void*&)pkDummy);
    }

    return fclose(m_pkFile) == 0;
}
//----------------------------------------------------------------------------
bool MgcStream::Load (char* acBuffer, int iSize)
{
    // TO DO.  implement
    return false;
}
//----------------------------------------------------------------------------
bool MgcStream::Save (char*& racBuffer, int& riSize)
{
    // TO DO.  implement
    return false;
}
//----------------------------------------------------------------------------
bool MgcStream::InsertInMap (MgcObject* pkKey, void* pvValue)
{
    return m_kMap.SetAt(pkKey,pvValue);
}
//----------------------------------------------------------------------------
MgcObject* MgcStream::GetFromMap (MgcObject* pkLinkID)
{
    Link* pkLink;
    if ( m_kMap.GetAt(pkLinkID,(void*&)pkLink) )
        return pkLink->GetObject();
    else
        return 0;
}
//----------------------------------------------------------------------------
void MgcStream::ReadString (char*& racString)
{
    int iLength = 0;
    fread(&iLength,sizeof(int),1,m_pkFile);
    if ( iLength > 0 )
    {
        racString = new char[iLength+1];
        fread(racString,sizeof(char),iLength,m_pkFile);
        racString[iLength] = 0;
    }
    else
    {
        racString = 0;
    }
}
//----------------------------------------------------------------------------
void MgcStream::WriteString (const char* acString)
{
    int iLength;
    if ( acString )
    {
        iLength = strlen(acString);
        fwrite(&iLength,sizeof(int),1,m_pkFile);
        fwrite(acString,sizeof(char),iLength,m_pkFile);
    }
    else
    {
        iLength = 0;
        fwrite(&iLength,sizeof(int),1,m_pkFile);
    }
}
//----------------------------------------------------------------------------
MgcStream::Link::Link (MgcObject* pkObject)
    :
    m_akLinkID(LINK_GROW_BY)
{
    m_pkObject = pkObject;
    m_uiQuantity = 0;
    m_uiCurrent = 0;
}
//----------------------------------------------------------------------------
void MgcStream::Link::Add (MgcObject* pkLinkID)
{
    unsigned int uiArrayQuantity = m_akLinkID.GetQuantity();
    if ( m_uiQuantity >= uiArrayQuantity )
    {
        uiArrayQuantity += LINK_GROW_BY;
        m_akLinkID.SetQuantity(uiArrayQuantity,true);
    }

    m_akLinkID[m_uiQuantity++] = pkLinkID;
}
//----------------------------------------------------------------------------
MgcObject* MgcStream::Link::GetObject ()
{
    return m_pkObject;
}
//----------------------------------------------------------------------------
unsigned int MgcStream::Link::GetQuantity () const
{
    return m_uiQuantity;
}
//----------------------------------------------------------------------------
MgcObject* MgcStream::Link::GetLinkID ()
{
    assert( m_uiCurrent < m_uiQuantity );
    return m_akLinkID[m_uiCurrent++];
}
//----------------------------------------------------------------------------
